// Copyright 2020 Apulis Technology Inc. All rights reserved.

package file

import (
	"fmt"
	"github.com/apulis/sdk/go-utils/storage/comm"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"syscall"
)

type MethodBackendFile struct {
	Cfg comm.HandlerCfg
}

func (s *MethodBackendFile) Init(cfg comm.HandlerCfg) error {
	s.Cfg = cfg
	return nil
}

func (s *MethodBackendFile) StoreFile(localFilePath string, dst string, perm os.FileMode) error {
	if !s.isValidSchema(dst) {
		return comm.ErrTypeInvalidSchema
	}

	dstFile, err := comm.GetSchemaFile(dst)
	if err != nil {
		return err
	}

	if s.isFileExist(dstFile) {
		return comm.ErrTypeDstFileExist
	}

	err = s.autoMkdir(dst)
	if err != nil {
		return err
	}

	input, err := ioutil.ReadFile(localFilePath)
	if err != nil {
		return fmt.Errorf("%v", err)
	}

	err = ioutil.WriteFile(dstFile, input, perm)
	if err != nil {
		return fmt.Errorf("%v", err)
	}

	return nil
}

func (s *MethodBackendFile) GetFile(src string, dstLocalPath string, perm os.FileMode) error {
	if !s.isValidSchema(src) {
		return comm.ErrTypeInvalidSchema
	}

	srcFile, err := comm.GetSchemaFile(src)
	if err != nil {
		return err
	}

	// 判断目标文件，避免被覆盖
	if s.isFileExist(dstLocalPath) {
		return comm.ErrTypeDstFileExist
	}

	input, err := ioutil.ReadFile(srcFile)
	if err != nil {
		return fmt.Errorf("%v", err)
	}

	err = ioutil.WriteFile(dstLocalPath, input, perm)
	if err != nil {
		return fmt.Errorf("%v", err)
	}

	return nil
}

func (s *MethodBackendFile) OpenFile(src string) (io.ReadCloser, error) {
	if !s.isValidSchema(src) {
		return nil, comm.ErrTypeInvalidSchema
	}

	srcFile, err := comm.GetSchemaFile(src)
	if err != nil {
		return nil, err
	}

	f, err := os.Open(srcFile)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func (s *MethodBackendFile) CopyFile(src string, dst string) error {
	if !s.isValidSchema(src) || !s.isValidSchema(dst) {
		return comm.ErrTypeInvalidSchema
	}

	srcFile, err := comm.GetSchemaFile(src)
	if err != nil {
		return err
	}

	dstFile, err := comm.GetSchemaFile(dst)
	if err != nil {
		return err
	}

	if s.isFileExist(dstFile) {
		return comm.ErrTypeDstFileExist
	}

	err = s.autoMkdir(dst)
	if err != nil {
		return err
	}

	info, err := os.Stat(srcFile)
	if err != nil {
		return err
	}

	input, err := ioutil.ReadFile(srcFile)
	if err != nil {
		return fmt.Errorf("%v", err)
	}

	// perm按srcFile原样复制
	err = ioutil.WriteFile(dstFile, input, info.Mode())
	if err != nil {
		return fmt.Errorf("%v", err)
	}

	return nil
}

func (s *MethodBackendFile) MoveFile(src string, dst string) error {
	if !s.isValidSchema(src) || !s.isValidSchema(dst) {
		return comm.ErrTypeInvalidSchema
	}

	srcFile, err := comm.GetSchemaFile(src)
	if err != nil {
		return err
	}

	dstFile, err := comm.GetSchemaFile(dst)
	if err != nil {
		return err
	}

	if s.isFileExist(dstFile) {
		return comm.ErrTypeDstFileExist
	}

	err = s.autoMkdir(dst)
	if err != nil {
		return err
	}

	err = os.Rename(srcFile, dstFile)
	if err != nil {
		return err
	}

	return err
}

func (s *MethodBackendFile) DeleteFile(dst string) error {
	if !s.isValidSchema(dst) {
		return comm.ErrTypeInvalidSchema
	}

	dstFile, err := comm.GetSchemaFile(dst)
	if err != nil {
		return err
	}

	if comm.IsDir(dstFile) {
		return comm.ErrTypeIsDir
	}

	err = os.Remove(dstFile)
	if err != nil {
		return err
	}

	return nil
}

func (s *MethodBackendFile) CopyDir(srcDir string, dstDir string) error {
	if !s.isValidSchema(srcDir) || !s.isValidSchema(dstDir) {
		return comm.ErrTypeInvalidSchema
	}

	srcDirLocal, err := comm.GetSchemaFile(srcDir)
	if err != nil {
		return err
	}

	dstDirLocal, err := comm.GetSchemaFile(dstDir)
	if err != nil {
		return err
	}

	err = os.MkdirAll(dstDirLocal, 0755)
	if err != nil {
		return err
	}

	err = comm.CopyDirectory(srcDirLocal, dstDirLocal)
	if err != nil {
		return err
	}

	return nil
}

func (s *MethodBackendFile) CopyFromLocalDir(srcDirLocal string, dstDir string) error {
	if !s.isValidSchema(dstDir) {
		return comm.ErrTypeInvalidSchema
	}

	dstDirLocal, err := comm.GetSchemaFile(dstDir)
	if err != nil {
		return err
	}

	err = os.MkdirAll(dstDirLocal, 0755)
	if err != nil {
		return err
	}

	err = comm.CopyDirectory(srcDirLocal, dstDirLocal)
	if err != nil {
		return err
	}

	return nil
}

func (s *MethodBackendFile) CopyFromRemoteDir(srcDir string, dstDirLocal string) error {
	if !s.isValidSchema(srcDir) {
		return comm.ErrTypeInvalidSchema
	}

	srcDirLocal, err := comm.GetSchemaFile(srcDir)
	if err != nil {
		return err
	}

	err = os.MkdirAll(dstDirLocal, 0755)
	if err != nil {
		return err
	}

	err = comm.CopyDirectory(srcDirLocal, dstDirLocal)
	if err != nil {
		return err
	}

	return nil
}

func (s *MethodBackendFile) MoveDir(srcDir string, dstDir string) error {
	if !s.isValidSchema(srcDir) || !s.isValidSchema(dstDir) {
		return comm.ErrTypeInvalidSchema
	}

	srcDirLocal, err := comm.GetSchemaFile(srcDir)
	if err != nil {
		return err
	}

	dstDirLocal, err := comm.GetSchemaFile(dstDir)
	if err != nil {
		return err
	}

	err = os.MkdirAll(dstDirLocal, 0755)
	if err != nil {
		return err
	}

	err = comm.MoveDirectory(srcDirLocal, dstDirLocal)
	if err != nil {
		return err
	}

	return nil
}

func (s *MethodBackendFile) MoveFromLocalDir(srcDirLocal string, dstDir string) error {
	if !s.isValidSchema(dstDir) {
		return comm.ErrTypeInvalidSchema
	}

	dstDirLocal, err := comm.GetSchemaFile(dstDir)
	if err != nil {
		return err
	}

	err = os.MkdirAll(dstDirLocal, 0755)
	if err != nil {
		return err
	}

	err = comm.MoveDirectory(srcDirLocal, dstDirLocal)
	if err != nil {
		return err
	}

	return nil
}

func (s *MethodBackendFile) MoveFromRemoteDir(srcDir string, dstDirLocal string) error {
	if !s.isValidSchema(srcDir) {
		return comm.ErrTypeInvalidSchema
	}

	srcDirLocal, err := comm.GetSchemaFile(srcDir)
	if err != nil {
		return err
	}

	err = os.MkdirAll(dstDirLocal, 0755)
	if err != nil {
		return err
	}

	err = comm.MoveDirectory(srcDirLocal, dstDirLocal)
	if err != nil {
		return err
	}

	return nil
}

func (s *MethodBackendFile) Mkdir(dst string) error {
	dstDirPath, err := comm.GetSchemaFile(dst)
	if err != nil {
		return err
	}

	err = os.MkdirAll(dstDirPath, 0755)
	if err != nil {
		return err
	}
	return nil
}

func (s *MethodBackendFile) DeleteDir(dstDir string, force bool) error {
	dstDirPath, err := comm.GetSchemaFile(dstDir)
	if err != nil {
		return err
	}

	if !comm.IsDir(dstDirPath) {
		return comm.ErrTypeInvalidDir
	}

	if force {
		return os.RemoveAll(dstDirPath)
	} else {
		return syscall.Rmdir(dstDirPath)
	}
}

// internal function
func (s *MethodBackendFile) isValidSchema(resource string) bool {
	schema, err := comm.GetSchema(resource)
	if err != nil {
		return false
	}

	return schema == comm.SCHEMA_TYPE_LOCALFILE
}

func (s *MethodBackendFile) isFileExist(localFile string) bool {
	_, err := os.Stat(localFile)
	if err == nil {
		return true
	}

	return false
}

func (s *MethodBackendFile) isRegularFile(localFile string) bool {
	info, err := os.Stat(localFile)
	if err != nil {
		return false
	}

	return info.Mode().IsRegular()
}

func (s *MethodBackendFile) autoMkdir(dst string) error {
	dstFilePath, err := comm.GetSchemaFile(dst)
	if err != nil {
		return err
	}

	dir := filepath.Dir(dstFilePath)
	err = os.MkdirAll(dir, 0755)
	if err != nil {
		return err
	}
	return nil
}
